package org.springframework.core.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Objects;

import static org.springframework.core.annotation.MergedAnnotations.*;

public class AnnotationsScanner {

    static <C, R> R scan(C annotationType,
                         AnnotatedElement annotatedElement,
                         SearchStrategy searchStrategy,
                         AnnotationsProcessor<C, R> processor) {
        R result = process(annotationType, annotatedElement, searchStrategy, processor);
        return processor.finish(result);
    }

    private static <C, R> R process(C annotationType,
                                    AnnotatedElement annotatedElement,
                                    SearchStrategy searchStrategy,
                                    AnnotationsProcessor<C, R> processor) {
        if (annotatedElement instanceof Class<?> annotatedElementClass) {
            return processClass(annotationType, annotatedElementClass, searchStrategy, processor);
        }

        if (annotatedElement instanceof Method annotatedElementMethod) {
            return processMethod(annotationType, annotatedElementMethod, searchStrategy, processor);
        }

        throw new RuntimeException();
    }

    private static <R, C> R processMethod(C annotationType,
                                          Method annotatedElementMethod,
                                          SearchStrategy searchStrategy,
                                          AnnotationsProcessor<C, R> processor) {

        switch (searchStrategy) {
            case DIRECT, INHERITED_ANNOTATIONS:
            case SUPERCLASS:
            case TYPE_HIERARCHY:
                return pricessMethodHierarchy(annotationType, new int[]{0}, annotatedElementMethod, processor);
            default:
                throw new RuntimeException();
        }
    }

    private static <R, C> R pricessMethodHierarchy(C annotationType,
                                                   int[] aggregateIndex,
                                                   Method annotatedElementMethod,
                                                   AnnotationsProcessor<C, R> processor) {
        return null;
    }

    private static <R, C> R processClass(C annotationType,
                                         Class<?> annotatedElementClass,
                                         SearchStrategy searchStrategy,
                                         AnnotationsProcessor<C, R> processor) {
        switch (searchStrategy) {
            case DIRECT:
                return processElement(annotationType, annotatedElementClass, processor);
            case INHERITED_ANNOTATIONS:
            case SUPERCLASS:
            case TYPE_HIERARCHY:
                return processClassHierarchy(annotationType, new int[]{0}, annotatedElementClass, processor);
            default:
                return null;
        }
    }

    private static <R, C> R processClassHierarchy(C annotationType,
                                                  int[] aggregateIndex,
                                                  Class<?> annotatedElementClass,
                                                  AnnotationsProcessor<C, R> processor) {
        R result = processor.doWithAggregate(annotationType, aggregateIndex[0]);
        if (result != null) {
            return result;
        }

        if (AnnotationFilter.PLAIN.matches(annotatedElementClass)) {
            return null;
        }

        Annotation[] declaredAnnotations = AnnotationUtils.getDeclaredAnnotations(annotatedElementClass);
        result = processor.doWithAnnotations(annotationType, aggregateIndex[0], annotatedElementClass, declaredAnnotations);
        if (result != null) {
            return result;
        }

        return null;
    }

    private static <R, C> R processElement(C context,
                                           Class<?> source,
                                           AnnotationsProcessor<C, R> processor) {
        return null;
    }
}
